package net.letskit.opengl.naterobins.tutors;

import com.sun.opengl.util.GLUT;
import java.awt.BorderLayout;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCanvas;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLJPanel;
import javax.media.opengl.glu.GLU;
import javax.swing.JFrame;
import java.awt.Point;

/**
 * The fog tutorial program demonstrates how to use depth cueing 
 * (or fog) in OpenGL. It includes command panels that allow dynamic update 
 * of the parameters for linear, exponential and exponential2 fog functions. 
 * The fog color can also be dynamically changed. 
 * 
 * @author kle
 */
public class FogTutor extends JFrame{

    private FogRenderer renderer;
    private GLJPanel canvass;
    private GLCapabilities caps;
    private final int GAP = 25;

    public FogTutor() {
        super();
        GraphicsDevice dev = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
        int w = dev.getDisplayMode().getWidth();
        int h = dev.getDisplayMode().getHeight();
        setSize(w, h);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        init();

    }

    private void init() {

        caps = new GLCapabilities();
        caps.setDoubleBuffered(true);
        caps.setHardwareAccelerated(true);
        caps.setDepthBits(24);

        canvass = new GLJPanel(caps);
        renderer = new FogRenderer(canvass);

        add(canvass, BorderLayout.CENTER);

        canvass.requestFocusInWindow();
//        canvass.setMinimumSize(new Dimension(512, 512));
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        FogTutor fogTutorial = new FogTutor();
        fogTutorial.setVisible(true);
    }
}

/**
 * 
 * @author kle
 */
class FogRenderer //
        implements //
        GLEventListener, //
        KeyListener, //
        MouseListener, //
        MouseMotionListener, //
        MouseWheelListener //
{

    /**
     * 
     */
    class Cell //
    {//====================================================

        int id;
        int x, y;
        float min, max;
        float value;
        float step;
        String info;
        String format;

        /**
         * 
         * @param id
         * @param x
         * @param y
         * @param min
         * @param max
         * @param value
         * @param step
         * @param info
         * @param format
         */
        Cell(int id, int x, int y, float min,
                float max, float value, float step,
                String info, String format) {
            this.id = id;
            this.x = x;
            this.y = y;
            this.min = min;
            this.max = max;
            this.value = value;
            this.step = step;
            this.info = info;
            this.format = format;
        }
    }//====================================================
    private String title = "Fog Tutorial";
    private GL gl;
    private GLU glu;
    private GLUT glut;
    //
    private Cell[] color = new Cell[4];
    private Cell density;
    private Cell fstart;
    private Cell fend;
    //
    private int mode = GL.GL_LINEAR;
    private int clear = GL.GL_TRUE;
    //
    private float eye[] = {0.5F, 0.25F, 1.5F};
    private float at[] = {0.0F, -0.25F, 0.0F};
    private float up[] = {0.0F, 1.0F, 0.0F};
    //
    private int sub_width = 256,  sub_height = 256;
    private int selection = 0;
    //
    private int font_style = GLUT.BITMAP_TIMES_ROMAN_10;
    //
    private static final int GAP = 25;
	//
	private boolean mousePressed = false;
	private Point mousePos = null;
    private GLAutoDrawable drawable;

    /**
     * 
     * @param parent
     */
    public FogRenderer(GLJPanel parent) {
            parent.addGLEventListener(this);
            parent.addMouseListener(this);
            parent.addMouseMotionListener(this);
            parent.addMouseWheelListener(this);
        parent.addKeyListener(this);
        }

    public FogRenderer(GLCanvas parent) {
            parent.addGLEventListener(this);
            parent.addMouseListener(this);
            parent.addMouseListener(this);
            parent.addMouseWheelListener(this);
        parent.addKeyListener(this);
        }

    private void setFont(String name, int size) {
        font_style = GLUT.BITMAP_HELVETICA_10;

        if (name.equals("helvetica")) {
            if (size == 12) {
                font_style = GLUT.BITMAP_HELVETICA_12;
            } else if (size == 18) {
                font_style = GLUT.BITMAP_HELVETICA_18;
            }
        } else if (name.equals("times roman")) {
            font_style = GLUT.BITMAP_TIMES_ROMAN_10;
            if (size == 24) {
                font_style = GLUT.BITMAP_TIMES_ROMAN_24;
            }
        } else if (name.equals("8x13")) {
            font_style = GLUT.BITMAP_8_BY_13;
        } else if (name.equals("9x15")) {
            font_style = GLUT.BITMAP_9_BY_15;
        }
    }

    /**
     * 
     * @param drawable
     * @param x
     * @param y
     * @param string
     */
    private void drawString(GLAutoDrawable drawable,
            int x, int y, String string) {
        gl = drawable.getGL();
        gl.glRasterPos2i(x, y);
        // print by character OR by string.
//        for (int i = 0; i < string.length(); i++) {
//            glut.glutBitmapCharacter(font_style, string.charAt(i));
//        }
        glut.glutBitmapString(font_style, string);
    }

    private void initCells() {
        color[0] = new Cell(1, 180, 40, 0.0F, 1.0F, 0.7F, 0.005F,
                "Specifies red component of fog color.", "%.2f");
        color[1] = new Cell(1, 180, 40, 0.0F, 1.0F, 0.7F, 0.005F,
                "Specifies red component of fog color.", "%.2f");
        color[2] = new Cell(1, 180, 40, 0.0F, 1.0F, 0.7F, 0.005F,
                "Specifies red component of fog color.", "%.2f");
        color[3] = new Cell(1, 180, 40, 0.0F, 1.0F, 0.7F, 0.005F,
                "Specifies red component of fog color.", "%.2f");

        density = new Cell(5, 260, 120, 0.0F, 3.0F, 1.0F, 0.01F,
                "Specifies the near distance.", "%.2f");
        fstart = new Cell(5, 240, 120, -3.0F, 3.0F, 0.5F, 0.01F,
                "Specifies the far distance.", "%.2f");
        fend = new Cell(7, 240, 160, -3.0F, 3.0F, 2.0F, 0.01F,
                "Specifies the fog density", "%.2f");
    }

    private void cell_draw(GLAutoDrawable drawable, Cell cell) {
        gl = drawable.getGL();
        gl.glColor3ub((byte) 0, (byte) 255, (byte) 128);
        if (selection == cell.id) {
            gl.glColor3ub((byte) 255, (byte) 255, (byte) 0);
            drawString(drawable, 10, 240, cell.info);
            gl.glColor3ub((byte) 255, (byte) 0, (byte) 0);
        }

    }

    private int cell_hit(Cell cell, int x, int y) {
        if (x > cell.x && x < cell.x + 60 && y > cell.y - 30 && y < cell.y + 10) {
            return cell.id;
        }

        return 0;
    }

    private void cell_update(Cell cell, int update) {
        if (selection != cell.id) {
            return;
        }

        cell.value += update * cell.step;
        if (cell.value < cell.min) {
            cell.value = cell.min;
        } else if (cell.value > cell.max) {
            cell.value = cell.max;
        }

    }

    private void cell_vector(float[] dst, Cell[] cell, int num) {
        while (--num >= 0) {
            dst[num] = cell[num].value;
        }

    }

    private void fogEquationReshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
        gl = drawable.getGL();
        gl.glViewport(x, y, width, height);
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glOrtho(0, width, height, 0, -1.0, 1.0);
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
        gl.glLineWidth(2.0F);
    }

    private void modelReshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
        float fog_color[] = new float[4];
        gl = drawable.getGL();
        gl.glViewport(x, y, width, height);
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        glu.gluPerspective(60.0, (float) width / height, 0.5, 8.0);
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
        glu.gluLookAt(eye[0], eye[1], eye[2], at[0], at[1], at[2], up[0], up[1], up[2]);
        if (clear == GL.GL_TRUE) {
            gl.glClearColor(color[0].value, color[1].value,
                    color[2].value, color[3].value);
        } else {
            gl.glClearColor(0.2F, 0.2F, 0.2F, 1.0F);
        }

        gl.glEnable(GL.GL_DEPTH_TEST);
        gl.glEnable(GL.GL_LIGHTING);
        gl.glEnable(GL.GL_LIGHT0);
        gl.glEnable(GL.GL_FOG);
        gl.glFogi(GL.GL_FOG_MODE, mode);
        gl.glFogf(GL.GL_FOG_START, fstart.value);
        gl.glFogf(GL.GL_FOG_END, fend.value);
        gl.glFogf(GL.GL_FOG_DENSITY, density.value);

        cell_vector(fog_color, color, 4);
        gl.glFogfv(GL.GL_FOG_COLOR, fog_color, 0);
    }

    private void commandReshape(GLAutoDrawable drawable,
            int x, int y, int width, int height) {
        gl = drawable.getGL();
        gl.glViewport(x, y, width, height);
        gl.glMatrixMode(gl.GL_PROJECTION);
        gl.glLoadIdentity();
        glu.gluOrtho2D(0, width, height, 0);
        gl.glMatrixMode(gl.GL_MODELVIEW);
        gl.glLoadIdentity();
        gl.glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
    }

    /**
     * Fog equation sub-window/viewport
     * @param drawable
     */
    private void fogEquationDisplay(GLAutoDrawable drawable) {
        gl = drawable.getGL();

        gl.glColor3ub((byte) 255, (byte) 255, (byte) 255);
        setFont("helvetica", 18);

        if (mode == gl.GL_LINEAR) {
            drawString(drawable, 100, 80, "end - z");
            drawString(drawable, 30, 95, "f   = ");
            drawString(drawable, 90, 110, "end - start");
            gl.glBegin(gl.GL_LINE_STRIP);
            gl.glVertex2i(85, 90);
            gl.glVertex2i(185, 90);
            gl.glEnd();
        } else {
            drawString(drawable, 90, 80, "- (density * z)");
            drawString(drawable, 30, 95, "f   =   e");
            if (mode == gl.GL_EXP2) {
                drawString(drawable, 208, 68, "2");
            }
        }

        setFont("helvetica", 12);
        drawString(drawable, 20, 180, "z is the distance in eye coordinates");
        drawString(drawable, 20, 195, "from origin to fragment being fogged.");

        drawable.swapBuffers();
    }

    /**
     * Draws model
     * @param drawable
     */
    private void modelDisplay(GLAutoDrawable drawable) {
        gl = drawable.getGL();
        //drawmodel();
        glut.glutSolidCube(0.8F);

        drawable.swapBuffers();

    }

    private void commandDisplay(GLAutoDrawable drawable) {
        gl = drawable.getGL();

        gl.glColor3ub((byte) 255, (byte) 255, (byte) 255);
        setFont("helvetica", 18);

        drawString(drawable, 20, color[0].y, "GLfloat color[4] = {");
        drawString(drawable, 230, color[0].y, "" + color[0].value + ",");
        drawString(drawable, 290, color[0].y, "" + color[1].value + ",");
        drawString(drawable, 350, color[0].y, "" + color[2].value + ",");
        drawString(drawable, 410, color[0].y, "" + color[3].value + "};");
        drawString(drawable, 20, 80, "glFogfv(GL_FOG_COLOR, color);");
        if (mode == gl.GL_LINEAR) {
            drawString(drawable, 20, fstart.y, "glFogf(GL_FOG_START,\t" + fstart.value);
            drawString(drawable, 290, fstart.y, ");");
            drawString(drawable, 20, fend.y, "glFogf(GL_FOG_END,\t" + fend.value);
            drawString(drawable, 290, fend.y, ");");
        } else {
            drawString(drawable, 20, density.y, "glFogf(GL_FOG_DENSITY,\t" + density.value);
            drawString(drawable, 310, density.y, ");");
        }

        String _mode = (mode == gl.GL_LINEAR ? "GL_LINEAR" : mode == gl.GL_EXP ? "GL_EXP" : "GL_EXP2");
        drawString(drawable, 20, 200, "glFogi(GL_FOG_MODE, " + _mode + ");");
        //+                (mode == gl.GL_LINEAR ? "GL_LINEAR" : mode == gl.GL_EXP ? "GL_EXP" : "GL_EXP2"));

        cell_draw(drawable, color[0]);
        cell_draw(drawable, color[1]);
        cell_draw(drawable, color[2]);
        cell_draw(drawable, color[3]);

        if (mode == gl.GL_LINEAR) {
            cell_draw(drawable, fstart);
            cell_draw(drawable, fend);
        } else {
            cell_draw(drawable, density);
        }

        if (selection != 0) {
            gl.glColor3ub((byte) 255, (byte) 255, (byte) 0);
            drawString(drawable, 10, 240, "Click on the arguments and move the mouse to modify values.");
        }

        drawable.swapBuffers();
    }

    public void init(GLAutoDrawable drawable) {
        gl = drawable.getGL();
        glu = new GLU();
        glut = new GLUT();
        this.drawable = drawable;
        initCells();
    }

    public void display(GLAutoDrawable drawable) {
        gl = drawable.getGL();

        gl.glClearColor(0.8F, 0.8F, 0.8F, 0.0F);
        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT);
        gl.glColor3ub((byte) 0, (byte) 0, (byte) 0);
        setFont("helvetica", 12);
        drawString(drawable, GAP, GAP - 5,
                "Fog equation");
        drawString(drawable, GAP + sub_width + GAP, GAP - 5,
                "Screen-space view");
        drawString(drawable, GAP, GAP + sub_height + GAP - 5,
                "Command manipulation window");


        fogEquationReshape(drawable,
                GAP, GAP + sub_height + GAP,
                sub_width, sub_height);
        fogEquationDisplay(drawable);

        modelReshape(drawable,
                GAP + sub_width + GAP, GAP,
                sub_width, sub_height);
        modelDisplay(drawable);


        commandReshape(drawable,
                GAP, GAP,
                sub_width + GAP + sub_width, sub_height);
        commandDisplay(drawable);


        drawable.swapBuffers();
    }

    public void reshape(GLAutoDrawable drawable, //
            int x, int y, int w, int h) {
        gl = drawable.getGL();
        gl.glViewport(x, y, w, h);
        gl.glMatrixMode(gl.GL_PROJECTION);
        gl.glLoadIdentity();
        glu.gluOrtho2D(0, w, h, 0);
        gl.glMatrixMode(gl.GL_MODELVIEW);
        gl.glLoadIdentity();

        sub_width = (int) ((w - GAP * 3F) / 2.0F);
        sub_height = (int) ((h - GAP * 3F) / 2.0F);
    }

    public void displayChanged(GLAutoDrawable drawable,//
            boolean modeChanged, boolean deviceChanged) {
    }

    public void keyTyped(KeyEvent e) {
        System.err.println(e.getKeyChar());

    }

    public void keyPressed(KeyEvent e) {
        System.err.println(e.getKeyChar());

    }

    public void keyReleased(KeyEvent e) {
        System.err.println(e.getKeyChar());
        switch (e.getKeyCode()) {
            case KeyEvent.VK_ESCAPE:
                System.exit(0);
                break;

            case KeyEvent.VK_L:
                mode = GL.GL_LINEAR;
                break;
            case KeyEvent.VK_E:
                mode = GL.GL_EXP;
                break;
            case KeyEvent.VK_X:
                mode = GL.GL_EXP2;
                break;
            case KeyEvent.VK_C:
                break;
            case KeyEvent.VK_R:
                break;
            default:
                break;
        }
        System.err.println(mode);
        drawable.swapBuffers();
    }

    public void mouseClicked(MouseEvent e) {

        System.err.println(e.getPoint().toString());
    }

    public void mousePressed(MouseEvent e) {
		if(e.getButton()==e.BUTTON1){
			mousePos=e.getPoint();
			mousePressed = true;
			System.out.println("MouseDown!");
		}
	}

    public void mouseReleased(MouseEvent e) {
		mousePressed = false;
		System.out.println("MouseReleased&");
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }

    public void mouseDragged(MouseEvent e) {
		if(mousePressed){
			int dir = (int) -(mousePos.getY()-e.getPoint().getY());
			for(int i=0;i<color.length;i++){
				if(/*cell_hit(color[i],(int)e.getPoint().getX(),(int)e.getPoint().getY())!=0*/ true){
					//color[i].value+=color[i].step*dir;
					cell_update(color[i],dir); 
        //System.err.println(e.getPoint().toString());
        System.out.println(""+color[i].value);
				}
			}
			System.out.println("MouseMoved? " + dir);
			mousePos=e.getPoint();
		}
    }

    public void mouseMoved(MouseEvent e) {
    }

    public void mouseWheelMoved(MouseWheelEvent e) {
        System.err.println(e.getScrollAmount());
    }
}
